#include <iostream>
#include <cassert>
#include <time.h>
#include <vector>
#include <mutex>
#include <thread>

#include "MemoryPool.hpp"
#include "StackAlloc.hpp"

/* Adjust these values depending on how much you trust your computer */
#define ELEMS 1000000
#define REPS 50

std::mutex g_mutex;

#if 0

int main()
{
	clock_t start;

	std::cout << "Copyright (c) 2013 Cosku Acay, http://www.coskuacay.com\n";
	std::cout << "Provided to compare the default allocator to MemoryPool.\n\n";

	/* Use the default allocator */
	StackAlloc<int, std::allocator<int> > stackDefault;
	start = clock();
	for (int j = 0; j < REPS; j++)
	{
		assert(stackDefault.empty());
		for (int i = 0; i < ELEMS / 4; i++) {
			// Unroll to time the actual code and not the loop
			g_mutex.lock();
			stackDefault.push(i);
			g_mutex.unlock();
			g_mutex.lock();
			stackDefault.push(i);
			g_mutex.unlock();
			g_mutex.lock();
			stackDefault.push(i);
			g_mutex.unlock();
			g_mutex.lock();
			stackDefault.push(i);
			g_mutex.unlock();
		}
		for (int i = 0; i < ELEMS / 4; i++) {
			// Unroll to time the actual code and not the loop
			g_mutex.lock();
			stackDefault.pop();
			g_mutex.unlock();
			g_mutex.lock();
			stackDefault.pop();
			g_mutex.unlock();
			g_mutex.lock();
			stackDefault.pop();
			g_mutex.unlock();
			g_mutex.lock();
			stackDefault.pop();
			g_mutex.unlock();
		}
	}
	std::cout << "Default Allocator Time: ";
	std::cout << (((double)clock() - start) / CLOCKS_PER_SEC) << "\n\n";

	/* Use MemoryPool */
	StackAlloc<int, MemoryPool<int> > stackPool;
	start = clock();
	for (int j = 0; j < REPS; j++)
	{
		assert(stackPool.empty());
		for (int i = 0; i < ELEMS / 4; i++) {
			// Unroll to time the actual code and not the loop
			stackPool.push(i);
			stackPool.push(i);
			stackPool.push(i);
			stackPool.push(i);
		}
		for (int i = 0; i < ELEMS / 4; i++) {
			// Unroll to time the actual code and not the loop
			stackPool.pop();
			stackPool.pop();
			stackPool.pop();
			stackPool.pop();
		}
	}
	std::cout << "MemoryPool Allocator Time: ";
	std::cout << (((double)clock() - start) / CLOCKS_PER_SEC) << "\n\n";

	std::cout << "Here is a secret: the best way of implementing a stack"
		" is a dynamic array.\n";

	/* Compare MemoryPool to std::vector */
	std::vector<int> stackVector;
	start = clock();
	for (int j = 0; j < REPS; j++)
	{
		assert(stackVector.empty());
		for (int i = 0; i < ELEMS / 4; i++) {
			// Unroll to time the actual code and not the loop
			g_mutex.lock();
			stackVector.push_back(i);
			g_mutex.unlock();
			g_mutex.lock();
			stackVector.push_back(i);
			g_mutex.unlock();
			g_mutex.lock();
			stackVector.push_back(i);
			g_mutex.unlock();
			g_mutex.lock();
			stackVector.push_back(i);
			g_mutex.unlock();
		}
		for (int i = 0; i < ELEMS / 4; i++) {
			// Unroll to time the actual code and not the loop
			g_mutex.lock();
			stackVector.pop_back();
			g_mutex.unlock();
			g_mutex.lock();
			stackVector.pop_back();
			g_mutex.unlock();
			g_mutex.lock();
			stackVector.pop_back();
			g_mutex.unlock();
			g_mutex.lock();
			stackVector.pop_back();
			g_mutex.unlock();
		}
	}
	std::cout << "Vector Time: ";
	std::cout << (((double)clock() - start) / CLOCKS_PER_SEC) << "\n\n";

	std::cout << "The vector implementation will probably be faster.\n\n";
	std::cout << "MemoryPool still has a lot of uses though. Any type of tree"
		" and when you have multiple linked lists are some examples (they"
		" can all share the same memory pool).\n";

	return 0;
}


#endif

#if 1

#include <chrono>

/* Use the default allocator */
std::mutex g_mutex_d;
StackAlloc<int, std::allocator<int> > g_stackDefault;
double g_d_cost_time_1 = 0;
double g_d_cost_time_2 = 0;
double g_d_cost_time_3 = 0;

StackAlloc<int, MemoryPool<int> > stackPool;
double g_m_cost_time_1 = 0;
double g_m_cost_time_2 = 0;
double g_m_cost_time_3 = 0;

/* Compare MemoryPool to std::vector */
std::mutex g_mutex_v;
std::vector<int> stackVector;
double g_v_cost_time_1 = 0;
double g_v_cost_time_2 = 0;
double g_v_cost_time_3 = 0;

void default_fun(double & cost_time)
{
	clock_t start;
	start = clock();
	for (int i = 0; i < ELEMS; i++)
	{
		g_mutex_d.lock();
		g_stackDefault.push(100);
		g_mutex_d.unlock();
	}

	cost_time = (double)(clock() - start);
}

void memory_fun(double & cost_time)
{
	clock_t start;
	start = clock();
	for (int i = 0; i < ELEMS; i++)
	{
		stackPool.push(100);
	}

	cost_time = (double)(clock() - start);
}

void vector_fun(double & cost_time)
{
	clock_t start;
	start = clock();
	for (int i = 0; i < ELEMS; i++)
	{
		g_mutex_v.lock();
		stackVector.push_back(100);
		g_mutex_v.unlock();
	}

	cost_time = (double)(clock() - start);
}

int main()
{
	std::thread d_thread_1(default_fun, std::ref(g_d_cost_time_1));
	std::thread d_thread_2(default_fun, std::ref(g_d_cost_time_2));
	std::thread d_thread_3(default_fun, std::ref(g_d_cost_time_3));

	std::thread m_thread_1(memory_fun, std::ref(g_m_cost_time_1));
	std::thread m_thread_2(memory_fun, std::ref(g_m_cost_time_2));
	std::thread m_thread_3(memory_fun, std::ref(g_m_cost_time_3));

	std::thread v_thread_1(vector_fun, std::ref(g_v_cost_time_1));
	std::thread v_thread_2(vector_fun, std::ref(g_v_cost_time_2));
	std::thread v_thread_3(vector_fun, std::ref(g_v_cost_time_3));

	while (1)
	{
		if (g_d_cost_time_1 != 0 && g_d_cost_time_2 != 0 && g_d_cost_time_3 != 0 && g_m_cost_time_1 != 0 && g_m_cost_time_2 != 0 && g_m_cost_time_3 != 0
			&& g_v_cost_time_1 != 0 && g_v_cost_time_2 != 0 && g_v_cost_time_3 != 0)
		{
			break;
		}
		std::this_thread::sleep_for(std::chrono::microseconds(500));
	}

	std::cout << "All thread finished." << std::endl;
	getchar();
	std::cout << "Default Size: " << g_stackDefault.size() << " and cost time: " << (g_d_cost_time_1 + g_d_cost_time_2 + g_d_cost_time_3) / CLOCKS_PER_SEC << std::endl;
	std::cout << "Memory Size: " << stackPool.size() << " and cost time: " << (g_m_cost_time_1 + g_m_cost_time_2 + g_m_cost_time_3) / CLOCKS_PER_SEC << std::endl;
	std::cout << "Vector Size: " << stackVector.size() << " and cost time: " << (g_v_cost_time_1 + g_v_cost_time_2 + g_v_cost_time_3) / CLOCKS_PER_SEC << std::endl;

	if (d_thread_1.joinable())
		d_thread_1.join();

	if (d_thread_2.joinable())
		d_thread_2.join();

	if (d_thread_3.joinable())
		d_thread_3.join();

	if (m_thread_1.joinable())
		m_thread_1.join();

	if (m_thread_2.joinable())
		m_thread_2.join();

	if (m_thread_3.joinable())
		m_thread_3.join();

	if (v_thread_1.joinable())
		v_thread_1.join();

	if (v_thread_2.joinable())
		v_thread_2.join();

	if (v_thread_3.joinable())
		v_thread_3.join();

	return 0;
}

#endif